TypeScript 98.3%
CSS 0.9%
Shell 0.7%
1// Plan d'étude personnalisé : génération, consultation, progression (items cochés).2import { NextResponse } from "next/server";3import { z } from "zod";4import { apiError, parseBody, requireEnrollment } from "@/lib/api.ts";5import { assertSameOrigin, requireUser } from "@/lib/auth/session.ts";6import { get, run } from "@/lib/db/index.ts";7import { generatePlan } from "@/lib/learning/plan.ts";8import { normalizeCourse } from "@/lib/learning/helpers.ts";9import { logActivity } from "@/lib/usage.ts";1011export async function GET(_req: Request, ctx: { params: Promise<{ course: string }> }) {12 try {13 const user = await requireUser();14 const course = normalizeCourse((await ctx.params).course);15 requireEnrollment(user.id, course);16 const plan = get<{ id: number; exam_date: string; config: string; plan: string; created_at: string }>(17 "SELECT id, exam_date, config, plan, created_at FROM study_plans WHERE user_id = ? AND course_code = ? AND active = 1 ORDER BY id DESC LIMIT 1",18 user.id, course19 );20 return NextResponse.json({21 plan: plan ? { id: plan.id, examDate: plan.exam_date, config: JSON.parse(plan.config), days: JSON.parse(plan.plan), createdAt: plan.created_at } : null,22 });23 } catch (e) {24 return apiError(e);25 }26}2728const createSchema = z.object({29 action: z.literal("create"),30 examDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),31 weekdays: z.array(z.number().int().min(0).max(6)).min(1),32 minutesPerSession: z.number().int().min(20).max(360),33 weeksScope: z.tuple([z.number().int().min(1).max(14), z.number().int().min(1).max(14)]),34});35const toggleSchema = z.object({36 action: z.literal("toggle"),37 planId: z.number().int().positive(),38 date: z.string(),39 itemIndex: z.number().int().min(0),40 done: z.boolean(),41});4243export async function POST(req: Request, ctx: { params: Promise<{ course: string }> }) {44 try {45 await assertSameOrigin();46 const user = await requireUser();47 const course = normalizeCourse((await ctx.params).course);48 requireEnrollment(user.id, course);49 const body = await parseBody(req, z.discriminatedUnion("action", [createSchema, toggleSchema]));5051 if (body.action === "create") {52 if (new Date(body.examDate) <= new Date()) {53 return NextResponse.json({ error: "La date d'examen doit être dans le futur." }, { status: 400 });54 }55 const days = generatePlan(user.id, course, {56 examDate: body.examDate,57 weekdays: body.weekdays,58 minutesPerSession: body.minutesPerSession,59 weeksScope: body.weeksScope,60 });61 if (!days.length) return NextResponse.json({ error: "Aucun jour disponible avant l'examen avec ces choix." }, { status: 400 });62 run("UPDATE study_plans SET active = 0 WHERE user_id = ? AND course_code = ?", user.id, course);63 const r = run(64 "INSERT INTO study_plans (user_id, course_code, exam_date, config, plan, active) VALUES (?, ?, ?, ?, ?, 1)",65 user.id, course, body.examDate,66 JSON.stringify({ weekdays: body.weekdays, minutesPerSession: body.minutesPerSession, weeksScope: body.weeksScope }),67 JSON.stringify(days)68 );69 logActivity(user.id, "plan", course, 60);70 return NextResponse.json({ ok: true, planId: Number(r.lastInsertRowid), days });71 }7273 // toggle74 const plan = get<{ id: number; plan: string }>(75 "SELECT id, plan FROM study_plans WHERE id = ? AND user_id = ? AND course_code = ?",76 body.planId, user.id, course77 );78 if (!plan) return NextResponse.json({ error: "Plan introuvable." }, { status: 404 });79 const days = JSON.parse(plan.plan) as { date: string; items: { done?: boolean }[] }[];80 const day = days.find((d) => d.date === body.date);81 if (!day || !day.items[body.itemIndex]) return NextResponse.json({ error: "Élément introuvable." }, { status: 404 });82 day.items[body.itemIndex].done = body.done;83 run("UPDATE study_plans SET plan = ? WHERE id = ?", JSON.stringify(days), plan.id);84 return NextResponse.json({ ok: true });85 } catch (e) {86 return apiError(e);87 }88}89